【网络】RST详解 您所在的位置:网站首页 计算机二级中rst 之间的关系 【网络】RST详解

【网络】RST详解

2024-07-09 05:39| 来源: 网络整理| 查看: 265

😀现实很近又很冷,梦想很远却很温暖 ​

欢迎关注

CSDN:https://blog.csdn.net/ndrg55?spm=1011.2124.3001.5343&type=blog语雀:https://www.yuque.com/u12089348

以下内容来源:https://mp.weixin.qq.com/s/Fr6o6gRiIUIspV9-jR9snw 作者:小白debug 注:内容有所改动

微信图片_20211102101700.jpg

文章目录 一、什么是RST二、怎么知道收到RST了?三、出现RST的场景有哪些1.端口不可用端口未监听正常情况端口未监听就一定会发RST吗? 程序崩溃RST和502的关系 2.Socket提前关闭本端提前关闭远端提前关闭 四、对方没收到RST,会怎么样?五、收到RST就一定会断开连接吗?为什么要校验是否在窗口范围内加了窗口校验就不能用RST攻击了吗盲猜seqchallenge ack利用challenge ack获取seq 六、总结

一、什么是RST

我们都知道TCP正常情况下断开连接是用四次挥手,那是正常时候的优雅做法。 但异常情况下,收发双方都不一定正常,连挥手这件事本身都可能做不到,所以就需要一个机制去强行关闭连接。 RST 就是用于这种情况,一般用来异常地关闭一个连接。它是一个TCP包头中的标志位。 正常情况下,不管是发出,还是收到置了这个标志位的数据包,相应的内存、端口等连接资源都会被释放。从效果上来看就是TCP连接被关闭了。 而接收到 RST的一方,一般会看到一个 connection reset 或 connection refused 的报错。 微信图片_20211102101909.jpg

二、怎么知道收到RST了?

我们知道内核跟应用层是分开的两层,网络通信功能在内核,我们的客户端或服务端属于应用层。应用层只能通过 send/recv 与内核交互,才能感知到内核是不是收到了RST。 ​

当本端收到远端发来的RST后,内核已经认为此链接已经关闭。此时如果本端应用层尝试去执行 读数据操作,比如recv,应用层就会收到 Connection reset by peer 的报错,意思是远端已经关闭连接。 640.gif 如果本端应用层尝试去执行写数据操作,比如send,那么应用层就会收到 Broken pipe 的报错,意思是发送通道已经坏了。 640.gif 这两个是开发过程中很经常遇到的报错,感觉大家可以把这篇文章放进收藏夹吃灰了,等遇到这个问题了,再打开来擦擦灰,说不定对你会有帮助。

三、出现RST的场景有哪些

RST一般出现于异常情况,归类为 对端的端口不可用 和 socket提前关闭。

1.端口不可用

端口不可用分为两种情况。要么是这个端口从来就没有"可用"过,比如根本就没监听**(listen)过;要么就是曾经"可用",但现在"不可用"了,比如服务突然崩**了。

端口未监听 正常情况

640.webp 服务端listen 方法会创建一个sock放入到全局的哈希表中。 此时客户端发起一个connect请求到服务端。服务端在收到数据包之后,第一时间会根据IP和端口从哈希表里去获取sock。 640.webp

如果服务端执行过listen,就能从全局哈希表里拿到sock。 ​

但如果服务端没有执行过listen,那哈希表里也就不会有对应的sock,结果当然是拿不到。此时,正常情况下服务端会发RST给客户端。 ​

端口未监听就一定会发RST吗?

不一定。上面提到,发RST的前提是正常情况下,我们看下源码。

// net/ipv4/tcp_ipv4.c // 代码经过删减 int tcp_v4_rcv(struct sk_buff *skb) { // 根据ip、端口等信息 获取sock。 sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest); if (!sk) goto no_tcp_socket; no_tcp_socket: // 检查数据包有没有出错 if (skb->len doff end_seq)) { goto discard; } // step 2:执行收到 RST 后该干的事情 if (th->rst) { if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) tcp_reset(sk); else tcp_send_challenge_ack(sk); goto discard; } }

收到RST包,第一步会通过tcp_sequence先看下这个seq是否合法,其实主要是看下这个seq是否在合法接收窗口范围内。如果不在范围内,这个RST包就会被丢弃。 至于接收窗口是个啥,我们先看下面这个图。 640.webp 这里黄色的部分,就是指接收窗口,只要RST包的seq不在这个窗口范围内,那就会被丢弃。

为什么要校验是否在窗口范围内

正常情况下客户端服务端双方可以通过RST来断开连接。假设不做seq校验,如果这时候有不怀好意的第三方介入,构造了一个RST包,且在TCP和IP等报头都填上客户端的信息,发到服务端,那么服务端就会断开这个连接。同理也可以伪造服务端的包发给客户端。这就叫RST攻击 ​

640.webp 受到RST攻击时,从现象上看,客户端老感觉服务端崩了,这非常影响用户体验。 如果这是个游戏,我相信多崩几次,第二天大家就不来玩了。 ​

实际消息发送过程中,接收窗口是不断移动的,seq也是在飞快的变动中,此时第三方是比较难构造出合法seq的RST包的,那么通过这个seq校验,就可以拦下了很多不合法的消息。 ​

加了窗口校验就不能用RST攻击了吗

**不是,只是增加了攻击的成本。**但如果想搞,还是可搞的。 ​

以下是面向监狱编程的环节。 希望大家只了解原理就好了,不建议使用。相信大家都不喜欢穿着蓝白条纹的衣服,拍纯狱风的照片。 ​

从上面可以知道,不是每一个RST包都会导致连接重置的,要求是这个RST包的seq要在窗口范围内,所以,问题就变成了,我们怎么样才能构造出合法的seq。

盲猜seq

窗口数值seq本质上只是个uint32类型。

struct tcp_skb_cb { __u32 seq; /* Starting sequence number */ }

如果在这个范围内疯狂猜测seq数值,并构造对应的包,发到目的机器,虽然概率低,但是总是能被试出来,从而实现RST攻击。这种乱棍打死老师傅的方式,就是所谓的合法窗口盲打(blind in-window attacks)。 觉得这种方式比较笨?那有没有聪明点的方式,还真有,但是在这之前需要先看下面的这个问题。 ​

challenge ack

我们需要了解一个问题,比如服务端在已连接(ESTABLISHED)状态下,如果收到客户端发来的第一次握手包(SYN),会怎么样? ​

以前我以为服务单会认为客户端憨憨了,直接RST连接。 但实际,并不是。

static bool tcp_validate_incoming() { struct tcp_sock *tp = tcp_sk(sk); /* 判断seq是否在合法窗口内 */ if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) { if (!th->rst) { // 收到一个不在合法窗口内的SYN包 if (th->syn) goto syn_challenge; } } /* * RFC 5691 4.2 : 发送 challenge ack */ if (th->syn) { syn_challenge: tcp_send_challenge_ack(sk); } }

当客户端发出一个不在合法窗口内的SYN包的时候,服务端会发一个带有正确的seq数据ACK包出来,这个ACK包叫 challenge ack。 640.webp 上图是抓包的结果,用scapy随便伪造一个seq=5的包发到服务端(端口9090),服务端回复一个带有正确seq值的challenge ack包给客户端(端口8888)。

利用challenge ack获取seq

上面提到的这个challenge ack ,仿佛为盲猜seq的老哥们打开了一个新世界。 在获得这个challenge ack后,攻击程序就可以以ack值为基础,在一定范围内设置seq,这样造成RST攻击的几率就大大增加了。 640.webp

六、总结 RST其实是TCP包头里的一个标志位,目的是为了在异常情况下关闭连接。内核收到RST后,应用层只能通过调用读/写操作来感知,此时会对应获得 Connection reset by peer 和Broken pipe 报错。发出RST后不需要得到对方的ACK确认包,因此RST丢失后对方不能立刻感知,但是通过下一次重传数据或keepalive心跳包可以导致RST重传。**收到RST包,不一定会断开连接,seq不在合法窗口范围内的数据包会被默默丢弃。**通过构造合法窗口范围内seq,可以造成RST攻击,这一点大家了解就好,千万别学!


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有